/**
 * 
 */
package za.co.sindi.oauth.server.servlet;

import java.io.IOException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;

import za.co.sindi.oauth.core.annotation.Service;
import za.co.sindi.oauth.core.context.ApplicationContext;
import za.co.sindi.oauth.core.context.web.servlet.ServletApplicationContext;
import za.co.sindi.oauth.core.context.web.servlet.http.HttpServletApplicationContext;
import za.co.sindi.oauth.core.scanner.ClassScanner;
import za.co.sindi.oauth.core.scanner.filter.impl.AnnotationTypeFilter;
import za.co.sindi.oauth.core.scanner.filter.impl.AssignableFromTypeFilter;
import za.co.sindi.oauth.core.scanner.impl.ClasspathClassScanner;
import za.co.sindi.oauth.core.scanner.impl.CombinedClassScanner;
import za.co.sindi.oauth.core.scanner.impl.WebArchiveClassScanner;
import za.co.sindi.oauth.core.service.DefaultServiceCatalog;
import za.co.sindi.oauth.core.service.ServiceDirector;
import za.co.sindi.oauth.core.service.director.HttpServiceDirector;
import za.co.sindi.oauth.exception.ConfigBuilderException;
import za.co.sindi.oauth.exception.ServiceDirectorException;
import za.co.sindi.oauth.server.config.ConfigBuilder;
import za.co.sindi.oauth.server.config.ModuleConfig;
import za.co.sindi.oauth.server.config.builder.DefaultConfigBuilder;

/**
 * @author Buhake Sindi
 * @since 25 March 2012
 *
 */
public class OAuthServlet extends GenericServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = 6068333142885518755L;

	public static final String MODULE_KEY = OAuthServlet.class.getName() + ".MODULE_KEY";
	
	private static final Logger logger = Logger.getLogger(OAuthServlet.class);
	private String oauthConfigFile = "/WEB-INF/oauth-config.xml"; 
	private String basePackageName;
	
	/* (non-Javadoc)
	 * @see javax.servlet.GenericServlet#destroy()
	 */
	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		if (logger.isInfoEnabled()) {
			logger.info("Finalizing...");
		}
		
		getServletContext().removeAttribute(MODULE_KEY);
		DefaultServiceCatalog.getInstance().clearServices();
		
		//Release Logger resources
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		if (classLoader == null) {
			classLoader = this.getClass().getClassLoader();
		}
		
		LogFactory.release(classLoader);
		
		if (logger.isInfoEnabled()) {
			logger.info("Finalization complete...");
		}
	}

	/* (non-Javadoc)
	 * @see javax.servlet.GenericServlet#init()
	 */
	@Override
	public void init() throws ServletException {
		// TODO Auto-generated method stub
		try {
			if (logger.isInfoEnabled()) {
				logger.info("Initializing...");
			}
			
			basePackageName = getServletConfig().getInitParameter("basePackageName");
			String config = getServletConfig().getInitParameter("config");
			if (config != null && !config.isEmpty()) {
				oauthConfigFile = config;
			}
			
			if (logger.isDebugEnabled()) {
				logger.debug("OAuth configuration file: " + oauthConfigFile + ".");
			}
			
			initializeModuleConfig();
			if (logger.isInfoEnabled()) {
				logger.info("Initialization complete...");
			}
		} catch (Throwable t) {
			// TODO Auto-generated catch block
			logger.error("Error initializing servlet." , t);
			throw new UnavailableException(t.getLocalizedMessage());
		}
	}

	/* (non-Javadoc)
	 * @see javax.servlet.GenericServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
	 */
	@Override
	public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		ServiceDirector director = null;
		ApplicationContext applicationContext = null;
		if (request.getScheme().startsWith("http")) {
			director = new HttpServiceDirector();
			applicationContext = new HttpServletApplicationContext(getServletContext(), (HttpServletRequest)request, (HttpServletResponse)response);
		} else {
			applicationContext = new ServletApplicationContext(getServletContext(), request, response);
		}
		
		if (director == null) {
			throw new ServletException("No ServiceDirector cannot be identified for scheme " + request.getScheme());
		}
		
		try {
			ModuleConfig moduleConfig = getModuleConfig(request);
			if (request.getAttribute(MODULE_KEY) == null) {
				request.setAttribute(MODULE_KEY, moduleConfig);
			}
			
			director.init(moduleConfig);
			director.execute(applicationContext);
		} catch (ServiceDirectorException e) {
			// TODO Auto-generated catch block
			throw new ServletException("service()", e);
		}
	}
	
	private void initializeModuleConfig() throws ConfigBuilderException {
		ConfigBuilder builder = new DefaultConfigBuilder();

		if (basePackageName != null) {
			//Instantiate the ClassScanner
			ClassScanner scanner = new CombinedClassScanner(new ClasspathClassScanner(), new WebArchiveClassScanner(getServletContext()));
			scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
			scanner.addIncludeFilter(new AssignableFromTypeFilter(za.co.sindi.oauth.core.service.Service.class));
			
			//Set it to the ConfigBuilder
			builder.setClassScanner(scanner);
			builder.setBasePackageName(basePackageName);
		}
		
		//Parse
		ModuleConfig moduleConfig = builder.build(getServletContext().getResourceAsStream(oauthConfigFile));
		if (moduleConfig != null) {
			getServletContext().setAttribute(MODULE_KEY, moduleConfig);
		}
	}
	
	private ModuleConfig getModuleConfig(ServletRequest request) {
		ModuleConfig moduleConfig = (ModuleConfig) request.getAttribute(MODULE_KEY);
		if (moduleConfig == null) {
			moduleConfig = (ModuleConfig) getServletContext().getAttribute(MODULE_KEY);
		}
		
		return moduleConfig;
	}
}
